// 组件基础类
class DJG {
  constructor (arg) {
    this.arg = arg
    this._actions = {}
    this.init()
  }

  init (widgetFamily = config.widgetFamily) {
    // 组件大小：small,medium,large
    this.widgetFamily = widgetFamily
    // 系统设置的key，这里分为三个类型：
    // 当没有key2时，获取key1，没有key1获取全局key的设置
    this.FILE_MGR_LOCAL = FileManager.local();
    this.MGR_DOCU = this.FILE_MGR_LOCAL.libraryDirectory();
    // 背景图片
    this.BACKGROUND_KEY = this.FILE_MGR_LOCAL.joinPath(
      this.MGR_DOCU, 'bg_' + this.SETTING_KEY,
    );
    // 本地，用于存储图片等
    this.SETTING_KEY = this.md5(Script.name())
    this.BACKGROUND_KEY = this.FILE_MGR_LOCAL.joinPath(this.FILE_MGR_LOCAL.documentsDirectory(), `bg_${this.SETTING_KEY}.jpg`)
    // 插件设置
    this.settings = this.getSettings();
    this.settings.lightColor = this.settings.lightColor || '#000000';// 白天字体颜色
    this.settings.darkColor = this.settings.darkColor || '#FFFFFF';// 晚上字体颜色
    this.settings.lightBgColor = this.settings.lightBgColor || '#dde7e7';// 白天背景颜色
    this.settings.darkBgColor = this.settings.darkBgColor || '#537895';// 晚上背景颜色
    this.backGroundColor = Color.dynamic(
      new Color(this.settings.lightBgColor),
      new Color(this.settings.darkBgColor),
    );
    this.fontColor = Color.dynamic(
      new Color(this.settings.lightColor),
      new Color(this.settings.darkColor),
    );
  }
  
  /**
   * 注册点击操作菜单
   * @param {string} name 操作函数名
   * @param {func} func 点击后执行的函数
   */
  registerAction (name, func) {
    this._actions[name] = func.bind(this)
  }

  /**
   * 生成操作回调URL，点击后执行本脚本，并触发相应操作
   * @param {string} name 操作的名称
   * @param {string} data 传递的数据
   */
  actionUrl (name = '', data = '') {
    let u = URLScheme.forRunningScript()
    let q = `act=${encodeURIComponent(name)}&data=${encodeURIComponent(data)}&__arg=${encodeURIComponent(this.arg)}&__size=${this.widgetFamily}`
    let result = ''
    if (u.includes('run?')) {
      result = `${u}&${q}`
    } else {
      result = `${u}?${q}`
    }
    return result
  }
  
  // 小组件警报
  async renderSmall () {
    const widget = new ListWidget()
    let text = widget.addText("⚠️\n\n该尺寸小组件暂未适配！")
    text.font = Font.blackMonospacedSystemFont(14)
    text.textColor=Color.red()
    text.centerAlignText()
    return widget
  }
  
  // 中组件警报
  async renderMedium () {
    const widget = new ListWidget()
    let text = widget.addText("⚠️\n\n该尺寸小组件暂未适配！")
    text.font = Font.blackMonospacedSystemFont(18)
    text.textColor=Color.red()
    text.centerAlignText()
    return widget
  }
  
  // 大组件警报
  async renderLarge () {
    const widget = new ListWidget()
    let text = widget.addText("⚠️\n\n该尺寸小组件暂未适配！")
    text.font = Font.blackMonospacedSystemFont(18)
    text.textColor=Color.red()
    text.centerAlignText()
    return widget
  }

  /**
   * base64 编码字符串
   * @param {string} str 要编码的字符串
   */
  base64Encode (str) {
    const data = Data.fromString(str)
    return data.toBase64String()
  }

  /**
   * base64解码数据 返回字符串
   * @param {string} b64 base64编码的数据
   */
  base64Decode (b64) {
    const data = Data.fromBase64String(b64)
    return data.toRawString()
  }

  /**
   * md5 加密字符串
   * @param {string} str 要加密成md5的数据
   */
  md5 (str) {
    function d(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function f(n,t,r,e,o,u){return d((c=d(d(t,n),d(e,u)))<<(f=o)|c>>>32-f,r);var c,f}function l(n,t,r,e,o,u,c){return f(t&r|~t&e,n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f(t&e|r&~e,n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u;n[t>>5]|=128<<t%32,n[14+(t+64>>>9<<4)]=t;for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h<n.length;h+=16)c=l(r=c,e=f,o=i,u=a,n[h],7,-680876936),a=l(a,c,f,i,n[h+1],12,-389564586),i=l(i,a,c,f,n[h+2],17,606105819),f=l(f,i,a,c,n[h+3],22,-1044525330),c=l(c,f,i,a,n[h+4],7,-176418897),a=l(a,c,f,i,n[h+5],12,1200080426),i=l(i,a,c,f,n[h+6],17,-1473231341),f=l(f,i,a,c,n[h+7],22,-45705983),c=l(c,f,i,a,n[h+8],7,1770035416),a=l(a,c,f,i,n[h+9],12,-1958414417),i=l(i,a,c,f,n[h+10],17,-42063),f=l(f,i,a,c,n[h+11],22,-1990404162),c=l(c,f,i,a,n[h+12],7,1804603682),a=l(a,c,f,i,n[h+13],12,-40341101),i=l(i,a,c,f,n[h+14],17,-1502002290),c=v(c,f=l(f,i,a,c,n[h+15],22,1236535329),i,a,n[h+1],5,-165796510),a=v(a,c,f,i,n[h+6],9,-1069501632),i=v(i,a,c,f,n[h+11],14,643717713),f=v(f,i,a,c,n[h],20,-373897302),c=v(c,f,i,a,n[h+5],5,-701558691),a=v(a,c,f,i,n[h+10],9,38016083),i=v(i,a,c,f,n[h+15],14,-660478335),f=v(f,i,a,c,n[h+4],20,-405537848),c=v(c,f,i,a,n[h+9],5,568446438),a=v(a,c,f,i,n[h+14],9,-1019803690),i=v(i,a,c,f,n[h+3],14,-187363961),f=v(f,i,a,c,n[h+8],20,1163531501),c=v(c,f,i,a,n[h+13],5,-1444681467),a=v(a,c,f,i,n[h+2],9,-51403784),i=v(i,a,c,f,n[h+7],14,1735328473),c=g(c,f=v(f,i,a,c,n[h+12],20,-1926607734),i,a,n[h+5],4,-378558),a=g(a,c,f,i,n[h+8],11,-2022574463),i=g(i,a,c,f,n[h+11],16,1839030562),f=g(f,i,a,c,n[h+14],23,-35309556),c=g(c,f,i,a,n[h+1],4,-1530992060),a=g(a,c,f,i,n[h+4],11,1272893353),i=g(i,a,c,f,n[h+7],16,-155497632),f=g(f,i,a,c,n[h+10],23,-1094730640),c=g(c,f,i,a,n[h+13],4,681279174),a=g(a,c,f,i,n[h],11,-358537222),i=g(i,a,c,f,n[h+3],16,-722521979),f=g(f,i,a,c,n[h+6],23,76029189),c=g(c,f,i,a,n[h+9],4,-640364487),a=g(a,c,f,i,n[h+12],11,-421815835),i=g(i,a,c,f,n[h+15],16,530742520),c=m(c,f=g(f,i,a,c,n[h+2],23,-995338651),i,a,n[h],6,-198630844),a=m(a,c,f,i,n[h+7],10,1126891415),i=m(i,a,c,f,n[h+14],15,-1416354905),f=m(f,i,a,c,n[h+5],21,-57434055),c=m(c,f,i,a,n[h+12],6,1700485571),a=m(a,c,f,i,n[h+3],10,-1894986606),i=m(i,a,c,f,n[h+10],15,-1051523),f=m(f,i,a,c,n[h+1],21,-2054922799),c=m(c,f,i,a,n[h+8],6,1873313359),a=m(a,c,f,i,n[h+15],10,-30611744),i=m(i,a,c,f,n[h+6],15,-1560198380),f=m(f,i,a,c,n[h+13],21,1309151649),c=m(c,f,i,a,n[h+4],6,-145523070),a=m(a,c,f,i,n[h+11],10,-1120210379),i=m(i,a,c,f,n[h+2],15,718787259),f=m(f,i,a,c,n[h+9],21,-343485551),c=d(c,r),f=d(f,e),i=d(i,o),a=d(a,u);return[c,f,i,a]}function a(n){for(var t="",r=32*n.length,e=0;e<r;e+=8)t+=String.fromCharCode(n[e>>5]>>>e%32&255);return t}function h(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e<t.length;e+=1)t[e]=0;for(var r=8*n.length,e=0;e<r;e+=8)t[e>>5]|=(255&n.charCodeAt(e/8))<<e%32;return t}function e(n){for(var t,r="0123456789abcdef",e="",o=0;o<n.length;o+=1)t=n.charCodeAt(o),e+=r.charAt(t>>>4&15)+r.charAt(15&t);return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return a(i(h(t=r(n)),8*t.length));var t}function u(n,t){return function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,16<o.length&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}(r(n),r(t))}function t(n,t,r){return t?r?u(t,n):e(u(t,n)):r?o(n):e(o(n))}
    return t(str)
  }
  
  /**
   * HTTP 请求接口
   * @param {string} url 请求的url
   * @param {bool} useCache 是否采用离线缓存（请求失败后获取上一次结果）
   * @param {bool} json 返回数据是否为 json，默认 true
   * @return {string | json | null}
   */
  async httpGet (url, useCache = true, json = true ) {
    let data = null
    const cacheKey = this.md5(url);
    if(useCache) {
      if(this.setUpTime(cacheKey.slice(-10)) || !Keychain.contains(cacheKey)) {
        try {
          let req = new Request(url)
          data = await (json ? req.loadJSON() : req.loadString())
         } catch (e) {log(e)}
      }
      // 存储缓存
      if(data) Keychain.set(cacheKey, json ? JSON.stringify(data) : data);
      if (!data && Keychain.contains(cacheKey)) {
        // 判断是否有缓存
        let cache = Keychain.get(cacheKey)
        data = json ? JSON.parse(cache) : cache
      }
    }else {
      try {
        let req = new Request(url)
        data = await (json ? req.loadJSON() : req.loadString())
      } catch (e) {log(e)}
      // 存储缓存
      if(data) Keychain.set(cacheKey, json ? JSON.stringify(data) : data);
      if (Keychain.contains(cacheKey)) {
        // 判断是否有缓存
        let cache = Keychain.get(cacheKey)
        data = json ? JSON.parse(cache) : cache
      }
    }
    return data;
  }
  
  /**
   * 获取远程图片内容
   * @param {string} url 图片地址
   * @param {bool} useCache 是否使用缓存（请求失败时获取本地缓存）
   */
  async getImageByUrl (url, useCache = true) {
    const cacheKey = this.md5(url)
    const cacheFile = FileManager.local().joinPath(FileManager.local().temporaryDirectory(), cacheKey)
    // 判断是否有缓存
    if (useCache && FileManager.local().fileExists(cacheFile)) {
      return Image.fromFile(cacheFile)
    }
    try {
      const req = new Request(url)
      const img = await req.loadImage()
      // 存储到缓存
      FileManager.local().writeImage(cacheFile, img)
      return img
    } catch (e) {
      // 没有缓存+失败情况下，返回自定义的绘制图片（红色背景）
      let ctx = new DrawContext()
      ctx.size = new Size(100, 100)
      ctx.setFillColor(Color.red())
      ctx.fillRect(new Rect(0, 0, 100, 100))
      return await ctx.getImage()
    }
  }
  // 设置刷新时间间隔
  setUpTime(cacheKey, time = 30) {
    cacheKey = cacheKey;
    const nowTime = +new Date;
    let lastTime = nowTime;
    Keychain.contains(cacheKey) ? 
      lastTime = parseInt(Keychain.get(cacheKey)) : Keychain.set(cacheKey, String(lastTime));
    if(lastTime < (nowTime - 1000*60*time) || lastTime == nowTime) {
      Keychain.set(cacheKey, String(nowTime))
      return true;
    }else {
      return false;
    }
  }

  /**
   * 渲染标题内容
   * @param {object} widget 组件对象
   * @param {string} icon 图标地址
   * @param {string} title 标题内容
   * @param {bool|color} color 字体的颜色（自定义背景时使用，默认系统）
   */
  async renderHeader (widget, icon, title, color, iconSize = 14, textOpacity =0.7, fontSize = 12, align = false) {
    let _title = ""
    let header = widget.addStack()
    if(align)header.addSpacer()
    header.centerAlignContent()
    if(icon.search('http')!=-1){
      let _icon = header.addImage(await this.getImageByUrl(icon))
      _icon.imageSize = new Size(iconSize, iconSize)
      _icon.cornerRadius = 4
      header.addSpacer(5)
      _title = header.addText(title)
    }else{
      _title = header.addText(icon+"  "+title)
    }
    _title.textColor = color
    _title.textOpacity = textOpacity
    _title.font = Font.boldSystemFont(fontSize)
    if(align)header.addSpacer()
    widget.addSpacer(6)
    return widget
  }

  /**
   * 注册点击操作菜单
   * @param {string} name 操作函数名
   * @param {func} func 点击后执行的函数
   */
  async registerAction (name, func) {
    this._actions[name] = func.bind(this)
  }
  
  // 使用提供的选项数组生成警报。
  async generateAlert(message,options,title = null) {
    let alert = new Alert()
    alert.title = title
    alert.message = message
    for (const option of options) {
      alert.addAction(option)
    }
    let response =  alert.presentAlert()
    return response
  }
  
  /**
   * 弹出输入框
   * @param title 标题
   * @param desc  描述
   * @param opt   属性
   * @returns {Promise<void>}
   */
  async setAlertInput (title, desc, opt = {}, isSave = true) {
    const a = new Alert();
    a.title = title;
    a.message = !desc ? '' : desc;
    Object.keys(opt).forEach((key) => {
      a.addTextField(opt[key], this.settings[key]);
    });
    a.addAction('确定');
    a.addCancelAction('取消');
    const id = await a.presentAlert();
    if (id === -1) return;
    const data = {};
    Object.keys(opt).forEach((key, index) => {
      data[key] = a.textFieldValue(index);
    });
    // 保存到本地
    if (isSave) {
      this.settings = { ...this.settings, ...data };
      return this.saveSettings();
    }
    return data;
  };
  /**
   * 获取截图中的组件剪裁图
   * 可用作透明背景
   * 返回图片image对象
   * @param {string} title 开始处理前提示用户截图的信息，可选（适合用在组件自定义透明背景时提示）
   */
  async cropBackgroundImage() {
    // 将图像裁剪到指定的 rect 中。
    function cropImage(img,rect) { 
      let draw = new DrawContext()
      draw.size = new Size(rect.width, rect.height)
      draw.drawImageAtPoint(img,new Point(-rect.x, -rect.y))  
      return draw.getImage()
    }
    // 确定用户是否已截屏
    var message
    message = "开始之前，请返回主屏幕并长按进入编辑模式。滑动到最右边的空白页并截图。"
    let exitOptions = ["设置背景","退出截屏"]
    let shouldExit = await this.generateAlert(message,exitOptions)
    if (shouldExit){return}
    // 获取屏幕截图并确定手机大小。
    let img = await Photos.fromLibrary()
    let height = img.size.height
    let phone = this.phoneSizes()[height]
    if (!phone) {
      message = "您似乎选择了非iPhone屏幕截图的图像，或者不支持您的iPhone。请使用其他图像再试一次。"
      await this.generateAlert(message,["OK"])
      return
    }
    //提示小部件的大小和位置。
    message = "您想要创建什么尺寸的小部件？"
    let sizes = ["小尺寸","中尺寸","大尺寸"]
    let size = await this.generateAlert(message,sizes)
    let widgetSize = sizes[size]
    message = "您想它在什么位置？"
    message += (height == 1136 ? " (请注意，您的设备仅支持两行小部件，因此中间和底部选项相同。)" : "")
    // 根据手机大小确定图像裁剪。
    let crop = { w: "", h: "", x: "", y: "" }
    if (widgetSize == "小尺寸") {
      crop.w = phone.small
      crop.h = phone.small
      let positions = ["左上角","右上角","中间左","中间右","左下角","右下角"]
      let _posotions = ["Top left","Top right","Middle left","Middle right","Bottom left","Bottom right"]
      let position = await this.generateAlert(message,positions)
      let keys = _posotions[position].toLowerCase().split(' ')
      crop.y = phone[keys[0]]
      crop.x = phone[keys[1]]
    
    } else if (widgetSize == "中尺寸") {
      crop.w = phone.medium
      crop.h = phone.small
      crop.x = phone.left
      let positions = ["顶部","中间","底部"]
      let _positions = ["Top","Middle","Bottom"]
      let position = await this.generateAlert(message,positions)
      let key = _positions[position].toLowerCase()
      crop.y = phone[key]
    
    } else if(widgetSize == "大尺寸") {
      crop.w = phone.medium
      crop.h = phone.large
      crop.x = phone.left
      let positions = ["顶部","底部"]
      let position = await this.generateAlert(message,positions)
      // 底部的大小部件具有"middle" y-value.
      crop.y = position ? phone.middle : phone.top
    }
    // 裁剪图像并完成小部件。
    return cropImage(img, new Rect(crop.x,crop.y,crop.w,crop.h))
    Script.complete()
  }
  // 所有受支持手机上的小部件的像素大小和位置。
  phoneSizes() {
      let phones = {
        // 12 and 12 Pro
      "2532": {small:474, medium:1014, large:1062, left:78, right:618, top:231, middle:819, bottom:1407},
      // 12 Pro max
      "2778": {small:518, medium:1114, large:1162, left:86, right:678, top:252, middle:898, bottom:1544},
      // 11 Pro Max, XS Max
      "2688": {small:507, medium:1080, large:1137, left:81, right:654, top:228, middle:858, bottom:1488},
      // 11, XR
      "1792": {small:338, medium:720, large:758, left:54, right:436, top:160, middle:580, bottom:1000},
      // 11 Pro, XS, X
      "2436": {small:465, medium:987, large:1035, left:69, right:591, top:213, middle:783, bottom:1353},
      // Plus phones
      "2208": {small:471, medium:1044, large:1071, left:99, right:672, top:114, middle:696, bottom:1278},
      // SE2 and 6/6S/7/8
      "1334": {small:296, medium:642, large:648, left:54, right:400, top:60, middle:412, bottom:764},
      // SE1
      "1136": {small:282, medium:584, large:622, left:30, right:332, top:59, middle:399, bottom:399},
      // 11 and XR in Display Zoom mode
      "1624": {small:310, medium:658, large:690, left:46, right:394, top:142, middle:522, bottom:902},
      // Plus in Display Zoom mode
      "2001" : {small:444, medium:963, large:972, left:81, right:600, top:90, middle:618, bottom:1146}
    }
    return phones
  }
  /**
   * 获取组件尺寸宽度大小
   * @param {string} size 组件尺寸【small】、【medium】、【large】
   */
  getIphoneSize() {
    let phoneSize = {};
    // 屏幕缩放比例
    let screenScale = Device.screenScale()
    // 组件宽度
    let phoneWidgetSize = undefined
    // 手机屏幕高度
    const screenHeight = Device.screenSize().height * screenScale;
    phoneWidgetSize = this.phoneSizes()[screenHeight];
    if (!phoneWidgetSize) {
      phoneSize = {width: 1125, height: 2436};
    } else {
      const screenWidth = Device.screenSize().width * screenScale;
      phoneSize = {width: screenWidth, height: screenHeight};
    }
    return phoneSize
  }
  
  /**
   * 弹出一个通知
   * @param {string} title 通知标题
   * @param {string} body 通知内容
   * @param {string} url 点击后打开的URL
   */
  async notify (title, body, url, opts = {}) {
    let n = new Notification()
    n = Object.assign(n, opts);
    n.title = title
    n.body = body
    if (url) n.openURL = url
    return await n.schedule()
  }
  
  // ######创建画布######
  makeCanvas(size) {
    const canvas = new DrawContext();
    canvas.opaque = false;
    canvas.respectScreenScale = true;
    canvas.size = new Size(size.width, size.height);
    return canvas;
  }
  
  // ######绘制文字#######  
  drawText(canvas, x, y, width,height,text,font,fontsize,alignment,color=Color.white()){
    if(alignment == "left") {canvas.setTextAlignedLeft()}
    if(alignment == "center") {canvas.setTextAlignedCenter()}
    if(alignment == "right") {canvas.setTextAlignedRight()}
    canvas.setFont(font)
    canvas.setTextColor(color)
    canvas.drawTextInRect(text, new Rect(x, y, width, height))  
  }

  /**
   * 给图片加一层半透明遮罩
   * @param {Image} img 要处理的图片
   * @param {string} color 遮罩背景颜色
   * @param {float} opacity 透明度
   */
  async shadowImage (img, opacity = 0.1, color = '#000000') {
    let ctx = new DrawContext()
    // 获取图片的尺寸
    ctx.size = img.size
    
    ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height']))
    ctx.setFillColor(new Color(color, opacity))
    ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height']))
    
    let res = await ctx.getImage()
    return res
  }

  /**
   * 获取当前插件的设置
   * @param {boolean} json 是否为json格式
   */
  getSettings(json=true){
    let res=json?{}:""
    let cache=""
    if (Keychain.contains(this.SETTING_KEY)) {
      cache= Keychain.get(this.SETTING_KEY)
    }
      if (json){
        try {
          res=JSON.parse(cache)
        } catch (e) {}
      }else{
        res=cache
      }
    return res
  }

  /**
   * 存储当前设置
   * @param {bool} notify 是否通知提示
   */
  saveSettings(notify=true){
    let res= (typeof this.settings==="object")?JSON.stringify(this.settings):String(this.settings)
    Keychain.set(this.SETTING_KEY, res)
    if (notify) this.notify("设置成功","桌面组件稍后将自动刷新")
  }
  
  // 设置 widget 背景图片
  async getWidgetBackgroundImage(widget) {
    const backgroundImage = await this.getBackgroundImage();
    if (backgroundImage) {
      widget.backgroundImage = backgroundImage;
    } else {
      widget.backgroundColor = this.backGroundColor;
    }
  };
  
  /**
   * 获取当前插件是否有自定义背景图片
   * @reutrn img | false
   */
  getBackgroundImage () {
    let result = null
    if (this.FILE_MGR_LOCAL.fileExists(this.BACKGROUND_KEY)) {
      result = Image.fromFile(this.BACKGROUND_KEY)
    }
    return result;
  }
  
  /**
   * 设置当前组件的背景图片
   * @param {image} img 
   */
  setBackgroundImage (img, notify = true) {
    if (!img) {
      // 移除背景
      if (this.FILE_MGR_LOCAL.fileExists(this.BACKGROUND_KEY)) {
        this.FILE_MGR_LOCAL.remove(this.BACKGROUND_KEY)
      }
      if (notify) this.notify("移除成功", "小组件背景图片已移除，稍后刷新生效")
    } else {
      // 设置背景
      // 全部设置一遍，
this.FILE_MGR_LOCAL.writeImage(this.BACKGROUND_KEY, img)
      if (notify) this.notify("设置成功", "小组件背景图片已设置！稍后刷新生效")
    }
  }
  /**
   * 获取定位
   * @param {int} time 执行获取定位时间间隔，默认 60
   */
  async getLocation(time = 60, flag = true) {
    let data = null;
    const cacheKey = 'Lock_location';
    if ((!Keychain.contains(cacheKey) || this.setUpTime('locUPtime', time)) && flag) {
      this.notify('正在获取位置', '请耐心等待几秒😊');
      try {
        data = await Location.current();
      }catch(e){
      	console.error(`${this.name}：${e}`);
       this.notify('😭位置获取失败', '请检查位置权限或网络设置。');
      };
    }
    if(data) {
      Keychain.set(cacheKey, JSON.stringify(data));
      this.notify('位置获取成功', '数据正在加载中。');
    }else if (!data && Keychain.contains(cacheKey)) {
      data = JSON.parse(Keychain.get(cacheKey));
    }
    return data;
  }
  
  colorRgba (sHex, alpha = 1) {
    // 十六进制颜色值的正则表达式
    var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
    /* 16进制颜色转为RGB格式 */
    let sColor = sHex.toLowerCase()
    if (sColor && reg.test(sColor)) {
      if (sColor.length === 4) {
        var sColorNew = '#'
        for (let i = 1; i < 4; i += 1) {
          sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
        }
        sColor = sColorNew
      }
      //  处理六位的颜色值
      var sColorChange = []
      for (let i = 1; i < 7; i += 2) {
        sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
      }
      return 'rgba(' + sColorChange.join(',') + ',' + alpha + ')'
    } else {
      return sColor
    }
  }

  async drawLineGraphs (data, canvasSize, altitude, color, alpha = 1) {
    const json = JSON.stringify(data);
    const html = `
    <canvas id="mainCanvas" />`;
    const js = `
    var canvas = document.createElement("canvas");
    canvas.width = ${canvasSize.width};
    canvas.height = ${canvasSize.height};
    var ctx = canvas.getContext('2d');
    var data = JSON.parse('${json}');
    var altitude = ${altitude};
    var func = ${this.colorRgba};
    var rgba = func('${color}', '${alpha}');
    var LineChart = function (ctx) {
        this.ctx = ctx;
        this.canvasWidth = this.ctx.canvas.width;
        this.canvasHeight = this.ctx.canvas.height;
        this.color = '${color}';
        this.space = 6;
        this.x0 = this.space;
        this.y0 = this.canvasHeight - this.space;
    }
    LineChart.prototype.init = function (data) {
        this.drawDotted(data);
        this.drawLine();
        this.drawColor(data);
    };
    LineChart.prototype.drawLine = function () {
        this.ctx.beginPath();
        this.ctx.moveTo(this.x0, this.y0);
        this.ctx.lineTo(this.canvasWidth-this.space, this.y0);
        this.ctx.lineWidth = 4
        this.ctx.stroke();
        this.ctx.fill();
    };
    LineChart.prototype.drawDotted = function (data) {
        var that = this;
        var prevCanvasX = 0;
        var prevCanvasY = 0;
        this.ctx.strokeStyle = this.color;
        var interval = (this.canvasWidth - this.space*2)/(data.length-1);
        data.forEach(function (item, i) {
            var canvasX = that.x0 + interval*i;
            var canvasY = that.y0 - (item + this.altitude);
            that.ctx.fillStyle = '${color}';
            that.ctx.beginPath();
            that.ctx.arc(canvasX, canvasY,that.space,0,2*Math.PI);
            that.ctx.closePath();
            that.ctx.fill();
            if(i != 0){
                that.ctx.beginPath();
                that.ctx.moveTo(prevCanvasX,prevCanvasY);
                that.ctx.lineTo(canvasX,canvasY);
                that.ctx.lineWidth = 3
                that.ctx.stroke();
            }
            prevCanvasX = canvasX;
            prevCanvasY = canvasY;
        });
    };
    
    LineChart.prototype.drawColor = function (data) {
        var interval = (this.canvasWidth - this.space*2)/(data.length-1);
        this.ctx.beginPath();
        this.ctx.fillStyle = rgba;
		this.ctx.moveTo(this.x0,this.y0);
		for(let i = 0; i < data.length; i++){
  		  this.ctx.lineTo(this.x0+interval*i, this.y0 - (altitude+data[i]));
		}
		this.ctx.lineTo((this.canvasWidth - this.space),this.y0);
		this.ctx.closePath();
		this.ctx.fill();
    };
    
    var lineChart = new LineChart(ctx);
    lineChart.init(data);
    output=canvas.toDataURL()`
    
    let wv = new WebView();
    await wv.loadHTML(html);
    const base64Image = await wv.evaluateJavaScript(js);
    const image = await new Request(base64Image).loadImage();
    return image;
  }
  
  async actionSetting () {
    const alert = new Alert()
    alert.title = "组件设置"
    alert.message = "配置基本信息，以便小组件展示"
    alert.addAction("字体颜色")
    alert.addAction("背景颜色")
    alert.addAction("清除背景图片")
    alert.addCancelAction("取消操作")
    const idx = await alert.presentSheet()
    if (idx == 0) {
      await this.setAlertInput("背景颜色","⒈白天   ⒉晚上\n请自行百度搜寻颜色(Hex 颜色)",{
        lightColor: '',
        darkColor: ''
      })
    }
    if (idx == 1) {
      await this.setAlertInput("字体颜色","⒈白天   ⒉晚上\n请自行百度搜寻颜色(Hex 颜色)",{
        lightBgColor: '',
        darkBgColor: ''
      })
    }
    if (idx == 2) {
      const index = await this.generateAlert("确定清除背景图片？",["确定","取消"]);
      if (index == 0){
        this.setBackgroundImage();
      }
    }
  }
}

// 测试环境
const Runing = async (Widget, default_args = "") => {
  let M = null
  // 判断hash是否和当前设备匹配
  if (config.runsInWidget) {
    M = new Widget(args.widgetParameter || '')
    const W = await M.render()
    Script.setWidget(W)
    Script.complete()
  } else {
    let { act, data, __arg, __size } = args.queryParameters
    M = new Widget(__arg || default_args || '')
    if (__size) 
    M.init(__size)
    if (!act || !M['_actions']) {
      // 弹出选择菜单
      const actions = M['_actions']
      const _actions = [
        // 预览组件
        async (debug = false) => {
          let a = new Alert()
          a.title = "预览组件"
          a.message = "测试桌面组件在各种尺寸下的显示效果"
          a.addAction("小尺寸")
          a.addAction("中尺寸")
          a.addAction("大尺寸")
          a.addCancelAction("取消操作")
          let i = await a.presentSheet()
          if (i === -1) return
          let w
          switch (i) {
            case 0:
              M.widgetFamily = 'small'
              w = await M.render()
              await w.presentSmall()
              break;
            case 1:
              M.widgetFamily = 'medium'
              w = await M.render()
              await w.presentMedium()
              break
            case 2:
              M.widgetFamily = 'large'
              w = await M.render()
              await w.presentLarge()
              break
            default:
              const func = funcs[i - 3];
              if (func) await func();
              break;
          }
          return i
        },
        // 透明背景
        async () => {
          const img = await M.cropBackgroundImage();
          if (img) M.setBackgroundImage(img)
        },
        // 组件设置
        async () => {
          await M.actionSetting()
        },
      ]
      const alert = new Alert()
      alert.title = M.name
      alert.message = M.desc
      alert.addAction("组件预览")
      alert.addAction("背景透明")
      alert.addAction("组件设置")
      for (let _ in actions) {
        alert.addAction(_)
        _actions.push(actions[_])
      }
      alert.addCancelAction("取消操作")
      const idx = await alert.presentSheet()
      if (_actions[idx]) {
        const func = _actions[idx]
        await func()
      }
      return
    }
    let _tmp = act.split('-').map(_ => _[0].toUpperCase() + _.substr(1)).join('')
    let _act = `action${_tmp}`
    if (M[_act] && typeof M[_act] === 'function') {
      const func = M[_act].bind(M)
      await func(data)
    }
  }
}
// await Runing( DJG ) 
module.exports = { DJG, Runing }
